Skip to content

feat(web): configuration-only optional frontend analytics (Umami + DataFinder) - #1037

Merged
XiaoSeS merged 28 commits into
iflytek:mainfrom
xiaoyaner0201:feat/configurable-frontend-analytics-989
Nov 3, 2026
Merged

XiaoSeS merged 28 commits into
iflytek:mainfrom
xiaoyaner0201:feat/configurable-frontend-analytics-989

Conversation

@xiaoyaner0201

@xiaoyaner0201 xiaoyaner0201 commented Oct 29, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Refs #989(两步交付的第二步。合并并验证后,可以关闭 #989)

按 #1010 合并时的约定和 OpenSpec openspec/changes/add-configurable-frontend-analytics/ 里的 PR-2 部分,把前端动作统计做成「改配置就能开」的可选功能。

  • 只改配置:Docker/Compose 和 Helm 用户填两项配置就能开启:SKILLHUB_WEB_ANALYTICS_CONFIG(JSON:enabled、adapter、consent、rules、adapterConfig)和 SKILLHUB_WEB_ANALYTICS_UPSTREAM(统计平台地址,只在部署侧使用,不会下发到浏览器)。不需要改 JS、复制文件或手写 Nginx。
  • 默认关闭、按需加载:页面挂载完成后才读取统计配置。没配置、enabled:false、consent:denied 或配置写错时,不加载采集模块和 SDK,也不发请求;页面启动不等待统计平台。
  • 故障隔离:配置写错只关闭统计,容器日志只提示出错的字段名,不回显原值;其他 runtime-config 字段保持不变。统计平台域名在请求时才解析,解析失败或平台停机都不影响 Web 启动和业务。
  • 单一产品源码:helper 和 Umami adapter 迁到 web/src/features/analytics/(通用类型、controller、loader、静态注册表、adapters/umami)。feat(web): add passive semantic analytics attributes across frontend workflows #1010 的两组测试一起迁入,断言没有改动。通用部分不认识 Umami 的字段,以后加平台只需新增一个注册的 adapter。
  • 同源代理:Web 容器启动时,只在开启后生成 /_skillhub-analytics/ 代理。只放行 Umami 的 script.js 和 api/send,其他路径都返回 404,不暴露管理界面。HTTPS 上游会校验证书并带正确的 SNI;代理会去掉 Cookie、Authorization、Referer。保留前缀已同步到 20-base-path.sh、base-path-config.ts、validate-release-config.sh 和 Helm 校验。子路径部署可用。
  • 没有新增依赖:没按设计稿引入 jq。用镜像自带的 awk 校验 JSON(限制 64 KiB、32 层、拒绝重复键),再用 base64 写进 runtime-config.js,浏览器端按数据解析。代价是多了一个约 90 行、有测试覆盖的小解析器。
  • 部署接线:compose.release.yml、.env.release.example 和 Helm 的 values、schema、ConfigMap、Deployment 都已透传,默认关闭。新增可选的 compose.analytics-umami.yml,用来自托管 Umami 和它自己的 Postgres:必须显式提供密码,管理端口只绑本机,不额外增加 Nginx。
  • 文档:docs/frontend-analytics-integration.md 改写成只改配置的中文接入指南,内容包括已有 Umami、自托管组合、动态授权、验收、排障、升级和关闭;docs/frontend-analytics.md 更新为英文契约。docs/examples/analytics/ 只保留一个指路说明。OpenSpec tasks.md 只勾选了本 PR 实际完成的项目。

第二个平台:DataFinder(火山引擎增长分析)

原在 #1050,按讨论合入本 PR(#1050 已关闭)。它和 Umami 共用同一套转换层、开关和代理框架,不涉及一键部署。

  • 只改配置:adapter: "datafinder",adapterConfig 只接受数字 appId 和可选的 product(默认 skillhub),upstream 填采集域名。其他字段全部拒绝,包括 App Key,防止误把服务端密钥放进前端。
  • 不引入官方 SDK:官方 JS SDK 约 250 KB,而且不是开源许可。适配器直接用 fetch(keepalive) 发 DataFinder Web 上报的最小 /list 负载:一个 batch、一个事件。不批量等待,不持久化,不重试,每次最长 3 秒。
  • 事件内容:事件名就是原有动作 ID(例如 skill.download.request),params 只含原生 event。公共属性只有 product 和 module:module 是动作所在页面的路由模板(例如 /space/$namespace/$slug),不含真实 namespace、slug、query 或 hash,未匹配的页面记为 other。
  • 匿名标识:生成随机 UUID v4 作为 user_unique_id/web_id,存在 localStorage,用于按浏览器统计 UV。不绑定 SkillHub 账号。纯 HTTP 内网部署没有 crypto.randomUUID,改用 getRandomValues 生成。存储不可用时只在当前页用内存 ID。文档写明这不等于无身份数据,由部署方评估。
  • 成功判定:只认 e:0 且 sc:1;HTTP 200 不算成功。
  • 代理:只放行 POST /_skillhub-analytics/list,Umami 的端点返回 404。沿用 TLS 校验,并清除 Cookie、Authorization、Referer。保留浏览器 User-Agent,因为平台会拒收非浏览器 UA。
  • 通用层改动:bootstrap 提供一个与平台无关的 currentPageType getter,依次经 loader、controller 传给 factory。Umami 不使用它。通用配置和 controller 不认识 DataFinder 的字段。awk 校验器增加数字值和按适配器分支。
  • 文档:接入指南新增第 2.1 节,说明配置、Helm、核对步骤、排障表(UA 拒收、sc:0、502)和隐私边界;英文契约和 OpenSpec PR-3 任务同步更新。

一键部署与独立管理(维护者第二轮反馈)

  • runtime.sh 两步部署:analytics-up 自动获取 compose.analytics-umami.yml,首次生成并保存独立密钥(<home>/analytics/.env,600 权限,已存在不覆盖),启动 Umami 和它自己的 Postgres,输出管理地址。用户改密、建网站拿到 UUID 后,运行 analytics-enable --website-id <UUID>:写入配置,Web 加入共享网络并重新创建。已有平台用 analytics-enable --website-id <UUID> --upstream <origin>,跳过第一步,也不建网络;DataFinder 用 --adapter datafinder --app-id <N> --upstream <origin>。另有 analytics-disable、analytics-down(只停止,不删卷)和 analytics-ps/logs/pull。
  • 两个独立 Compose 项目:统计平台使用独立项目名和数据卷。只有 Web(经 compose.release.analytics.yml)和 Umami 加入外部网络 skillhub-analytics,网络由脚本幂等创建、由 Compose 声明。开启后普通 up 自动带上这层网络配置,Web 重建后仍然连接。clean 保留统计平台的密钥和文件。
  • Kubernetes 独立部署单元:deploy/k8s/analytics-umami/umami.yaml 在独立 namespace 中部署 Postgres(PVC)、Umami 和两个 ClusterIP Service,Secret 由部署方创建。SkillHub Chart 只填写 web.analytics,经 Service DNS 连接,不依赖 Umami。
  • 真实集群冒烟脚本:charts/skillhub/tests/analytics-smoke.sh 核对事件真实入库,并断言只改统计配置时 Web Pod 替换、Server Pod UID 不变。
  • 文档:按三条路径分别说明:一键脚本新建平台、一键脚本接已有平台、Helm。写明文件如何获取、UUID 在哪一步取得、配置如何生效,以及关闭、升级和数据保留;运行边界保持不变。
  • 测试清理:旧 PR 的静态 fixture(pr1-compose.yaml、pr1-nginx.conf)已删除,仍需要的约束改为断言当前生产配置。新增 scripts/tests/runtime-analytics-test.sh,已加入 CI。
  • --aliyun:该模式从 OSS 根目录下载文件。OSS 目前没有新增的两个 Compose 文件(实测 404),需要在发布时同步上传,文档已写明。

Validation

基于最新 main(已合并 a681e43e 之后的 main),head ffc9c834。

corepack pnpm@10.33.0 -C web test        # 270 文件 / 1,432 项通过(含 DataFinder)
corepack pnpm@10.33.0 -C web typecheck   # 通过
corepack pnpm@10.33.0 -C web lint        # 通过
corepack pnpm@10.33.0 -C web build       # 通过
node web/scripts/analytics-manifest.mjs  # 通过,清单内容逐字节不变
python3 scripts/tests/web-analytics-test.py          # 10 项通过(启动脚本、配置序列化、恶意 JSON、代理片段)
bash scripts/tests/web-base-path-routing-test.sh     # 通过
bash scripts/tests/validate-release-config-test.sh   # 通过
bash scripts/tests/web-base-path-nginx-smoke-test.sh # 通过(真实 nginx 容器)
bash scripts/tests/nginx-forwarded-proto-test.sh     # 通过;结尾的 `ip_args[@]: unbound variable` 在 main 上同样出现,与本 PR 无关
helm lint --strict charts/skillhub -f charts/skillhub/tests/test-values.yaml   # 通过(helm 3.19.0 容器)
cd docs/skillhub && npm ci && npm run build                                  # 通过(站内 include 页含新内容)
bash charts/skillhub/tests/configuration-contracts.sh                        # 通过;含「只改统计仅 Web 更新、改后端配置后端仍更新」的 checksum 对照
docker compose --env-file .env.release.example -f compose.release.yml config
docker compose --env-file .env.release.example -f compose.release.yml -f compose.analytics-umami.yml config   # 需要提供密码,不提供时会按设计报错

一键部署实测(本机真实 Docker,隔离项目名和端口):

  • 空目录:runtime.sh up 后 Web 健康和业务 API 均为 200;analytics-up、建网站、analytics-enable 后,经同源代理发送事件 200,Umami API 和数据库都查到入库。
  • 重复执行:analytics-up/up 前后密钥哈希、server 容器和事件数都不变。
  • Web --force-recreate 后,默认网络和共享网络都还在,已存事件不变。
  • analytics-down 时业务健康 200;再 analytics-up,数据还在。
  • analytics-disable 后采集路由 404,业务 API 200。
  • 已有平台:--upstream 接入后事件数 1 → 2,Web 没有加入本地统计网络。

真实集群(kind): Umami/Postgres 部署在独立 stats namespace,SkillHub 是单独的 Helm Release。analytics-smoke.sh 结果:代理发送 200,Umami 数据库事件数 3 → 4。只改统计配置的 helm upgrade 替换了 Web Pod,Server Pod UID 不变。

限制:

  • kind 中 Chart 固定的 Bitnami PostgreSQL 镜像在迁移 V65 报 could not compute MD5 hash: unsupported,这与本 PR 无关。集群验收改用独立的官方 postgres:16 + externalDatabase,所以不能说明固定镜像这条路径已通过。
  • 入库验证用的是模拟浏览器 UA 的 HTTP 请求,没有做真实浏览器点击。
  • 完整的 S3/auth 冒烟没有跑。

另外做了本地真实运行验收:用本 PR 构建的前端、仓库里的 Nginx 模板和启动脚本,配合 Umami 3.4.1 与 Postgres,在真实 Chrome 中完成 26 项检查,Umami 实际入库 9 条事件。检查覆盖:

  • 默认零模块、零请求
  • 开启后页面挂载完成才加载
  • 实际出站请求只包含白名单字段
  • 配置写错、adapter 未知、上游地址非法时,只关闭统计
  • 域名无法解析、Umami 停机时,业务不受影响
  • 未注册的代理路径返回 404
  • 调用 stop() 后不再发送
  • /skillhub/ 子路径部署
  • 5 MiB 上传仍能到达 API

SkillHub 后端 API 是合成响应,不能作为后端业务的端到端证明。

还没覆盖的部分:

  • 镜像离线构建时,Corepack 需要联网,所以没有在离线环境完成整镜像构建。真实运行验收使用同样的 dist、模板和启动脚本组装容器完成。
  • OpenSpec 任务 6.3 中完整的 release Compose S3/auth 冒烟,以及 6.1、6.4 的评审和验收环节,都留给维护者。

Risk

  • User-facing impact: 默认关闭,未配置时不加载采集代码、不发请求,行为和现在一致。
  • Deployment or migration impact: Web 镜像新增一个启动脚本和一个 awk 文件,Compose 和 Helm 新增可选的环境变量;没有数据库或后端变更,也没有新增 npm、apk 依赖。/_skillhub-analytics/ 成为保留前缀,SKILLHUB_WEB_BASE_PATH 不能以它开头。修改配置后需要重新创建 Web(Compose 用 up -d --force-recreate web,restart 不会重新注入环境变量),新打开的页面才会生效;已经打开的旧标签页不会因关闭配置立即停止,需要刷新或关闭。Helm 的统计值放在 Web 专用 ConfigMap,只改统计配置时仅 Web 滚动更新,后端 Pod 不替换。
  • Rollback approach: 清空 SKILLHUB_WEB_ANALYTICS_CONFIG 并重新创建 Web,新页面即完全关闭;回退镜像版本也可以。

Notes

  • Related issue: [Feature] 支持厂商中立的前端业务语义属性 #989
  • 新平台适配除了注册前端 adapter,还要在 Web 启动脚本加该平台的部署侧配置校验和精确代理端点,并补测试。
  • 配置校验只检查格式:websiteId 是合法 UUID 但 Umami 中没有该网站时,api/send 返回 400 Website not found,事件不入库。排障表已补充。
  • Follow-up work: 其他平台的 adapter 按需另提;一键部署统计平台另开 PR;本 PR 不包含后端业务事件([Feature] RFC:默认关闭的厂商中立后端业务事件出口 / Opt-in backend business-event export #1009)、同意弹窗、重试队列或管理后台。
  • Docs or operator runbooks updated when behavior changed: 已更新 docs/frontend-analytics-integration.md、docs/frontend-analytics.md、docs/09-deployment.md 和 chart README 的入口链接。
English

Also included: the DataFinder (Volcengine growth analytics) adapter from #1050 (now closed). Config accepts only a numeric appId and optional product; no official SDK, a bounded fetch to a POST-only /_skillhub-analytics/list proxy; success requires e:0/sc:1; anonymous browser UUID; route templates only. Also adds one-click deployment: runtime.sh analytics-up / analytics-enable (independent Compose projects sharing one external network; secrets generated once and preserved), an independent Kubernetes Umami unit, three documented paths, and fixture cleanup. Verified with real Docker Compose scenarios and a kind cluster (event stored; analytics-only upgrade rolls only Web).

Refs #989 (second step; #989 can be closed once this is merged and verified).

Implements the PR-2 part of openspec/changes/add-configurable-frontend-analytics/, as agreed when #1010 was merged:

  • Configuration only: set SKILLHUB_WEB_ANALYTICS_CONFIG (JSON: enabled, adapter, consent, rules, adapterConfig) and SKILLHUB_WEB_ANALYTICS_UPSTREAM (deploy-side origin, never sent to the browser) through Docker/Compose or Helm. No JS, file copying or hand-written Nginx.
  • Default off, loaded on demand: analytics config is read after the app mounts. Absent config, enabled:false, consent:denied or invalid config means no analytics chunk, no SDK and no request. App startup never waits on the collector.
  • Failure isolation: invalid config only disables analytics, with field-level diagnostics that never echo raw values. Other runtime-config keys stay intact. The collector host is resolved per request, so DNS failure or collector downtime does not affect Web startup or business flows.
  • Single product source: the helper and Umami adapter moved to web/src/features/analytics/ (types, controller, loader, static registry, adapters/umami). Both feat(web): add passive semantic analytics attributes across frontend workflows #1010 test suites moved with their assertions unchanged. Generic code has no Umami fields, and new platforms are added as registered adapters.
  • Same-origin proxy: generated at startup only when enabled. Only Umami script.js and api/send are proxied under /_skillhub-analytics/; all other paths return 404. HTTPS upstreams are verified with correct SNI, and Cookie, Authorization and Referer are stripped. The reserved prefix is synced across the base-path checks and Helm validation, and sub-path deployment works.
  • No new dependency: instead of jq, the image's own awk validates the JSON (64 KiB, 32 levels, duplicate keys rejected). Base64 carries it into runtime-config.js, where the browser parses it as data. The trade-off is a small, tested parser of about 90 lines.
  • Deployment wiring: Compose, .env.release.example and Helm values, schema, ConfigMap and Deployment pass the config through, with analytics off by default. The optional compose.analytics-umami.yml overlay self-hosts Umami and its own Postgres, requires explicit secrets, binds the admin port to loopback only and adds no extra Nginx.
  • Docs: a Chinese configuration-only guide and an updated English contract. docs/examples/analytics/ is now a pointer only. OpenSpec ticks cover only the work done in this PR.

Validation at head ffc9c834 merged with latest main:

  • Frontend: 1,432 tests (including DataFinder); typecheck, lint, build and manifest (byte-identical) all pass.
  • Script tests: 10 new startup/serialization tests; base-path routing, release-config validation, nginx sub-path smoke and forwarded-proto all pass. The trailing unbound variable from forwarded-proto also appears on main.
  • Compose: default and overlay render. The overlay requires secrets by design.
  • Helm: helm lint --strict and configuration-contracts.sh pass (helm 3.19.0 container). Analytics lives in a Web-only ConfigMap; the contract test asserts analytics-only changes roll only Web and backend changes still roll the server. These are template checks; no real-cluster install was run.

A local real-runtime run (this PR's dist with the repo's Nginx template and entrypoints, plus Umami 3.4.1, Postgres and real Chrome) passed 26 checks, with 9 events stored. Covered: default zero requests, enabled loading, allowlisted payload, invalid config, DNS failure and collector downtime isolation, 404 for unregistered paths, stop(), /skillhub/ sub-path and a 5 MiB upload. SkillHub API responses were synthetic.

Not run locally:

  • An offline full image build, because Corepack needs the network.
  • The full release Compose S3/auth smoke, left to maintainers.

Rollback: clear SKILLHUB_WEB_ANALYTICS_CONFIG and recreate Web (docker compose restart does not re-read the env file). Old tabs keep collecting until reloaded or closed.

Notes: new platforms need a registered adapter plus deploy-side validation, proxy endpoints and tests. A valid UUID for a website that does not exist in Umami gets 400 Website not found and stores nothing; passing config validation does not prove collection.

Move the passive helper and Umami adapter into one product source, retain the existing assertions, and add consent-aware lazy lifecycle control.

Refs iflytek#989

Signed-off-by: 千乘妍 (Xiaoyaner) <258399167+xiaoyaner0201@users.noreply.github.com>
Safely transport bounded JSON using existing image tools, generate isolated exact proxy routes with verified HTTPS, and wire Compose and Helm with closed defaults.

Refs iflytek#989

Signed-off-by: 千乘妍 (Xiaoyaner) <258399167+xiaoyaner0201@users.noreply.github.com>
Replace manual injection instructions with Compose, Docker and Helm configuration, explicit platform initialization, consent controls, privacy boundaries and verified task status.

Refs iflytek#989

Signed-off-by: 千乘妍 (Xiaoyaner) <258399167+xiaoyaner0201@users.noreply.github.com>
Clarify the one-time initialization sequence verified during the guide walkthrough.

Refs iflytek#989

Signed-off-by: 千乘妍 (Xiaoyaner) <258399167+xiaoyaner0201@users.noreply.github.com>
Helm --set-string parses {...} as a list, so the enabled-analytics
contract case failed schema validation in CI. Use a values file, the
same way the operator guide documents the setting.

Refs iflytek#989

Signed-off-by: 千乘妍 (Xiaoyaner) <258399167+xiaoyaner0201@users.noreply.github.com>
@XiaoSeS

XiaoSeS commented Oct 30, 2026

Copy link
Copy Markdown
Collaborator

我们基于当前提交 6cee2fe 做了一轮本地核验,整体方案可行:配置化接入、默认关闭、按需加载和适配器扩展的方向都符合预期。

本次使用源码构建的真实镜像、真实 SkillHub 后端和 Umami 验证了:

  • 根路径、子路径下的登录、上传下载和事件采集。
  • 实际事件入库、发送字段白名单、停止与恢复采集。
  • 错误配置、统计平台停机、DNS/证书失败时的业务隔离。
  • Nginx 端点和方法限制,以及敏感请求头过滤。

这些主要链路均通过,暂未发现阻断性的浏览器或代理运行问题。建议本 PR 再调整以下两部分:

1. 隔离 Helm 统计配置的影响

目前统计字段放在共享 ConfigMap 中,而后端 Pod 的 checksum 覆盖整份 ConfigMap。因此仅修改统计开关、规则、网站 UUID 或上游地址,也会触发后端 Pod 替换;部分存储配置使用 Recreate,影响会更明显。

我们已通过 Helm 模板渲染对照确认。建议将统计配置拆到 Web 专用 ConfigMap,或收窄后端 checksum 的输入范围,并补测试:只改统计配置时,仅 Web 更新;修改后端配置时,后端仍正常更新。无需为此大范围重构 Chart。

2. 对齐文档与验收记录

  • 修改 .env.release 后,应明确需要重新创建 Web;单纯 restart 不会重新注入环境变量。
  • 关闭配置只影响重新加载配置的页面,旧标签页不会立即停止;指南已有说明,PR 正文同步即可。
  • OpenSpec 中仍有 Helm“未验证”的旧描述,请按实际执行情况更新,并区分模板检查与真实集群验证。
  • 新平台适配除 adapter 外,还涉及部署侧配置校验、代理端点和测试,正文可简短补充。
  • 排查说明可补充:UUID 格式合法但网站不存在时,实测 Umami 返回 400 Website not found,不会入库;配置校验通过不代表采集成功。

一键部署增强可以另开 PR,不要求本次完成。

建议后续在现有 runtime.sh 中增加可选的统计平台部署模式,使用独立 Compose 项目、配置目录和数据卷,与 SkillHub 分别管理启停和升级。接入采用两步:

  1. 部署 Umami+数据库,修改初始密码、创建网站并取得 UUID。
  2. 填写 SkillHub 统计配置,重新创建 Web,开启采集。

已有统计平台的部署方跳过第一步。文档说明网络连通、密钥复用、升级和备份即可,不必为了“一键”增加自动创建网站的逻辑。

当前 PR 保留现有配置化接入和可选 Compose 方式即可。完成上述 Helm 隔离和文档调整后,我们再核验受影响部分。

@xiaoyaner0201

xiaoyaner0201 commented Nov 1, 2026 •

Copy link
Copy Markdown
Contributor Author

感谢细致的核验,两部分都已按建议调整,新 head 是 f8ab71e2。

1. Helm 统计配置隔离

统计的两个值从共享 ConfigMap 移到 Web 专用的 <release>-web-analytics ConfigMap,后端 checksum 的输入不再包含它们,Chart 其他部分没有动。

configuration-contracts.sh 新增对照测试,渲染改动前后的 Deployment checksum:

  • 只改统计配置:Web 的 checksum 变化,后端和其他 Deployment 的 checksum 全部不变
  • 改后端配置(例如 bootstrapAdmin.displayName):后端 checksum 仍然变化

在旧模板上跑这组测试会失败;我也试过把统计值同时留在共享 ConfigMap,测试同样会拦下。helm lint --strict 和整套契约测试在 helm 3.19.0 容器里通过。

2. 文档与验收记录

  • 接入指南、英文契约和 .env.release.example 都写明:改 .env.release 后要 up -d --force-recreate web,restart 不会重新注入环境变量
  • PR 正文已同步:关闭配置后,旧标签页要刷新或关闭才会停止
  • OpenSpec 4.3、6.2 按实际执行更新:Helm 只做了模板检查(lint、契约测试),真实集群安装没有跑
  • 新平台适配:指南和正文补充说明,除了前端 adapter,还要改启动脚本里的部署侧校验、代理端点,并补测试
  • 排障表补充 400 Website not found:UUID 合法但 Umami 中没有这个网站时不会入库,配置校验通过不代表采集成功

一键部署按你们的建议另开 PR。辛苦再核验一下受影响的部分。

Move analytics values into a Web-only ConfigMap. The server checksum hashes
the shared ConfigMap, so analytics-only changes used to replace server pods.
Contract tests render before/after values and assert that analytics-only
changes roll only Web while backend changes still roll the server.

Docs: Compose restart does not re-read .env.release, old tabs keep the old
config, a valid but unknown Umami website returns 400 and stores nothing,
and new adapters also need deploy-side validation, proxy endpoints and
tests. OpenSpec now separates executed Helm template checks from the
real-cluster verification that has not been run.

Signed-off-by: 千乘妍 (Xiaoyaner) <258399167+xiaoyaner0201@users.noreply.github.com>
@xiaoyaner0201
xiaoyaner0201 force-pushed the feat/configurable-frontend-analytics-989 branch from 47285e4 to f8ab71e Compare November 1, 2026 12:52
Refs iflytek#989

Signed-off-by: 千乘妍 (Xiaoyaner) <258399167+xiaoyaner0201@users.noreply.github.com>
Refs iflytek#989

Signed-off-by: 千乘妍 (Xiaoyaner) <258399167+xiaoyaner0201@users.noreply.github.com>
crypto.randomUUID only exists in secure contexts and newer browsers, so
fresh browsers on plain-HTTP intranet deployments dropped every event.
Fall back to a getRandomValues v4 UUID with the same format.

Refs iflytek#989

Signed-off-by: 千乘妍 (Xiaoyaner) <258399167+xiaoyaner0201@users.noreply.github.com>
@XiaoSeS

XiaoSeS commented Nov 2, 2026

Copy link
Copy Markdown
Collaborator

我们对当前 PR 做了源码 Review 和本地真实部署验证。配置开启后的事件能够进入 Umami,关闭采集、错误配置、统计服务故障、停止采集及根路径/子路径等场景也进行了核验,目前没有发现新的阻断级运行问题。

前面 Review 提到的“修改统计配置连带触发后端滚动更新”,已经通过 Web 专用 ConfigMap 解决,模板对照验证也已通过。

继续梳理生产部署后,主要还有以下需要补齐的内容。

1. 一键部署用户的接入链路还不完整

目前 runtime.sh 不会获取和管理统计平台配套文件,文档中的部分操作依赖源码目录、Makefile 和手工 Compose 命令。通过一键脚本安装的用户,无法直接照着完成整个流程。

建议沿用现有脚本入口,提供两步部署:

  1. 部署统计平台:自动获取配套文件、首次生成并保存密钥、启动 Umami 和独立数据库,输出管理地址。
  2. 用户登录 Umami、修改默认密码、创建网站并取得 UUID;填写配置后,开启 SkillHub 采集。

已有统计平台的部署方直接填写地址和 UUID,跳过第一步。无需修改 JS、Nginx,也不要求克隆源码。

2. 统计平台与 SkillHub 分开管理

当前可选 Compose 文件是叠加到同一个项目里的。建议调整为两个独立 Compose 项目:

  • 各自管理数据库、数据卷及启停、升级。
  • 只有 SkillHub Web 和 Umami 加入共享的外部网络。
  • 网络创建和连接由脚本及 Compose 声明管理,容器重建后仍然有效。
  • 重复运行不覆盖已有密钥或数据,普通停止操作不删除数据。

这样统计平台可以单独维护,SkillHub 仍按原有方式部署和升级。

3. Helm 提供对应的独立部署路径

当前 SkillHub Chart 已支持配置统计地址和 UUID,但不负责安装 Umami。

建议保持两个独立 Release:统计平台与 SkillHub 分别部署,通过 Kubernetes Service/DNS 连接;不把 Umami 作为 SkillHub 的强制依赖。

如果提供自建统计平台的 Helm 部署,需要补齐独立部署配套,说明 Secret、持久化、管理入口及升级方式。已有平台的用户只需配置 SkillHub values。

验证方面,已有真实集群的基础安装/升级记录,但不能代替本次开启采集后的集群验证;仍需补测实际采集,以及仅修改统计配置时只滚动 Web。

4. 文档与验收同步补齐

建议文档按三条路径分别说明:

  • 一键脚本部署 SkillHub,并新建统计平台。
  • 一键脚本部署 SkillHub,连接已有统计平台。
  • Helm 部署并接入统计平台。

重点说明文件如何获取、UUID 在哪一步取得、配置如何生效,以及关闭、升级和数据保留方式。运行边界也请保留:旧标签页需刷新才能使用新配置;统计故障期间的事件不保证补发;格式正确但填错 UUID 仍可能无法采集。

新增部署流程请覆盖:空目录安装、已有实例接入、重复执行、容器重建后网络与数据保留、统计独立启停,以及关闭采集后的业务回归。

另外有一项非阻断的维护建议:部分测试仍断言旧 PR 的静态配置 fixture,可以考虑把仍需保留的约束迁到当前生产配置测试中,再清理重复 fixture。

整体采集架构可以沿用,主要是补齐部署交付闭环。这部分可以拆成后续 PR,但希望明确交付范围与跟踪事项,避免文档把当前能力描述为完整的一键接入。

以上是结合本地验证提出的建议。如果你有更简单、维护成本更低的实现方式,也欢迎讨论;核心目标是配置化接入、部署方便,并控制对现有 SkillHub 的影响。

Signed-off-by: 千乘妍 (Xiaoyaner) <258399167+xiaoyaner0201@users.noreply.github.com>
Signed-off-by: 千乘妍 (Xiaoyaner) <258399167+xiaoyaner0201@users.noreply.github.com>
@xiaoyaner0201 xiaoyaner0201 changed the title feat(web): configuration-only optional frontend analytics feat(web): configuration-only optional frontend analytics (Umami + DataFinder) Nov 2, 2026
@xiaoyaner0201

Copy link
Copy Markdown
Contributor Author

感谢这轮细致的部署验证 🙏 建议我们都认同。想把范围拆一下,方便本 PR 先收口:

本 PR(#1037)

后续 PR:一键部署闭环

按你们的四点建议单独交付:

  1. runtime.sh 增加可选的统计平台模式,分两步:部署 Umami+独立数据库(自动获取配套文件、首次生成并保存密钥、输出管理地址),然后填写 UUID 开启采集。已有平台可以跳过第一步。
  2. 统计平台改为独立 Compose 项目,只有 Web 和 Umami 加入由脚本声明的外部网络;重复执行不覆盖已有密钥和数据,普通停止不删除数据。
  3. Helm 使用两个独立 Release,通过 Service/DNS 连接,补齐 Secret、持久化、管理入口和升级说明。
  4. 文档按三条路径分别写,验收覆盖空目录安装、已有实例接入、重复执行、重建后网络与数据保留、统计独立启停、关闭后业务回归,以及开启采集后的真实集群验证和“只改统计配置时只滚动 Web”。旧 fixture 测试的迁移清理也放在后续 PR。

这些会写进 OpenSpec 任务作为跟踪项。这样拆可以吗?

Signed-off-by: 千乘妍 (Xiaoyaner) <258399167+xiaoyaner0201@users.noreply.github.com>
Signed-off-by: 千乘妍 (Xiaoyaner) <258399167+xiaoyaner0201@users.noreply.github.com>
Signed-off-by: 千乘妍 (Xiaoyaner) <258399167+xiaoyaner0201@users.noreply.github.com>
Signed-off-by: 千乘妍 (Xiaoyaner) <258399167+xiaoyaner0201@users.noreply.github.com>
Signed-off-by: 千乘妍 (Xiaoyaner) <258399167+xiaoyaner0201@users.noreply.github.com>
Signed-off-by: 千乘妍 (Xiaoyaner) <258399167+xiaoyaner0201@users.noreply.github.com>
Signed-off-by: 千乘妍 (Xiaoyaner) <258399167+xiaoyaner0201@users.noreply.github.com>
Signed-off-by: 千乘妍 (Xiaoyaner) <258399167+xiaoyaner0201@users.noreply.github.com>
Signed-off-by: 千乘妍 (Xiaoyaner) <258399167+xiaoyaner0201@users.noreply.github.com>
Signed-off-by: 千乘妍 (Xiaoyaner) <258399167+xiaoyaner0201@users.noreply.github.com>
Both raw bases publish compose.analytics-umami.yml at the root, so the
Aliyun and GitHub branches fetched the same path. No behavior change.

Signed-off-by: 千乘妍 (Xiaoyaner) <258399167+xiaoyaner0201@users.noreply.github.com>
@xiaoyaner0201

Copy link
Copy Markdown
Contributor Author

@XiaoSeS 部署闭环已经按四点建议在本 PR 补齐,按步骤拆成了多个提交方便逐个看。当前 head 是 ffc9c834。

1. 一键部署两步接入

  • runtime.sh analytics-up:自动获取配套文件,首次生成并保存密钥(重复执行不覆盖),启动 Umami 和独立数据库,并输出管理地址。
  • 用户改密、建网站后执行 analytics-enable --website-id <UUID>,即可开启采集。
  • 已有平台加 --upstream <origin>,跳过第一步。
  • 关闭用 analytics-disable。analytics-down 只停止服务,不删除数据。

不需要克隆源码,也不用改 JS 或 Nginx。

2. 独立管理

  • 统计平台是独立 Compose 项目,有自己的数据库和数据卷。
  • 只有 Web 和 Umami 加入外部网络。网络由脚本幂等创建,并在 Compose 中声明。
  • 开启后,普通 up 和重建 Web 都会保持连接。
  • clean 不会删除统计平台的密钥。

3. Kubernetes

  • deploy/k8s/analytics-umami/ 在独立 namespace 部署 Umami、Postgres(PVC)和 Service,Secret 由部署方创建。
  • SkillHub Chart 只需配置 web.analytics,通过 Service DNS 连接。

4. 文档和验收

文档按三条路径分别说明。以下场景都在本机真实运行过:

  • 空目录安装
  • 已有平台接入
  • 重复执行(密钥和数据不变)
  • Web 重建后网络和数据保留
  • 统计平台独立启停
  • 关闭采集后的业务回归

kind 集群中,Umami 部署在独立 namespace,SkillHub 是单独的 Release。开启采集后事件确实写入了 Umami 数据库;只改统计配置时只有 Web Pod 被替换,Server Pod UID 不变。脚本在 charts/skillhub/tests/analytics-smoke.sh。

旧 fixture 测试已改为断言当前生产配置。

需要说明的两点

  • kind 中 Chart 固定的 Bitnami PostgreSQL 镜像在迁移 V65 报 could not compute MD5 hash: unsupported,与本 PR 无关。所以集群验收改用独立的 postgres:16 外部数据库。
  • --aliyun 模式从 OSS 根目录下载文件,新增的两个 Compose 文件需要在发布时同步上传到 OSS。

详细结果见 PR 正文。辛苦再核验一下 🙏

@xiaoyaner0201

Copy link
Copy Markdown
Contributor Author

补充一条 DataFinder 的真实平台验收(head ffc9c834)。

我们用本分支 web/ 构建的镜像连接了一套私有化 DataFinder,在真实 Chrome 里打开 /search 并提交了一次搜索。

  • 页面挂载后才按需加载 controller 和 DataFinder adapter 两个 chunk,没有加载任何远程 SDK。
  • 浏览器只向同源的 POST /_skillhub-analytics/list 发了 1 个请求,负载是一个 batch、一个事件:
    • event 为 search.query.submit,params 为 {"event":"submit"}
    • header.custom 为 {product, module:"/search"}
    • 不含搜索词
  • 平台返回 HTTP 200,响应体为 {"e":0,"message":"success","sc":1,"tc":1}。
  • 首页上只有导航类标签,不在默认规则里,所以点击它们不会产生请求,符合预期。

平台事件列表的元数据通常会延迟一段时间出现,入库统计确认后我会再补充在这里。

Signed-off-by: XiaoSeS <87064762+XiaoSeS@users.noreply.github.com>
Signed-off-by: XiaoSeS <87064762+XiaoSeS@users.noreply.github.com>
Signed-off-by: XiaoSeS <87064762+XiaoSeS@users.noreply.github.com>
Signed-off-by: XiaoSeS <87064762+XiaoSeS@users.noreply.github.com>
Signed-off-by: XiaoSeS <87064762+XiaoSeS@users.noreply.github.com>
Signed-off-by: XiaoSeS <87064762+XiaoSeS@users.noreply.github.com>
@XiaoSeS
XiaoSeS merged commit 6c8ecd8 into iflytek:main Nov 3, 2026
30 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants